﻿using Blazored.LocalStorage;
using BlazorAdmin.Models;
using BlazorAdmin.Interface;
using BlazorAdmin.Security;
using BlazorAdmin.Web.Shared.Services;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.IdentityModel.Tokens;
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Text.Json;

namespace BlazorAdmin.Web.Shared.Providers
{
    /// <summary>
    /// Blazor框架认证组件
    /// </summary>
    public class AuthenticationProvider : AuthenticationStateProvider
    {
        private const string JWT = "jwt";
        private static readonly JwtSecurityTokenHandler tokenHandler = new();
        private static readonly SymmetricSecurityKey securityKey = new(Encoding.UTF8.GetBytes(BootstrapAppContext.JWT_SecurityKey));
        private static readonly SigningCredentials signingCredentials = new(securityKey, SecurityAlgorithms.HmacSha256);
        private static readonly ClaimsPrincipal AnonymousUser = new(new ClaimsIdentity());
        private static readonly AuthenticationState AnonymousState = new(AnonymousUser);
        private readonly BootstrapAppContext appContext;
        private readonly CyptoUtils cypto;
        private readonly ILocalStorageService localStorage;
        private readonly IUser userService;
        /// <summary>
        /// 构造方法
        /// </summary>
        /// <param name="localStorage"></param>
        /// <param name="userService"></param>
        /// <param name="appContext">资源上下文</param>
        public AuthenticationProvider(ILocalStorageService localStorage, IUser userService, BootstrapAppContext appContext)
        {
            this.localStorage = localStorage;
            this.userService = userService;
            this.appContext = appContext;
            cypto = new CyptoUtils("Lens2023", "Lens2023");
        }
        #region override

        /// <summary>
        /// 获取认证状态
        /// </summary>
        /// <returns></returns>
        public override async Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            AuthenticationState result = AnonymousState;
            string jwtToken = await GetLocalJwt();
            if (!string.IsNullOrEmpty(jwtToken))
            {
                JwtUserData? userData = GetJwtUserData(jwtToken);
                if (userData != null && userData.ExpiredTime > DateTime.Now)
                {
                    AppContextInit(userData);
                    result = null == userData ? AnonymousState : new AuthenticationState(appContext.ClaimsPrincipal!);
                }
            }
            return result;
        }

        #endregion

        /// <summary>
        /// 标记授权
        /// </summary>
        /// <returns></returns>
        public void MarkUserAsAuthenticated(string jwt)
        {
            if (null == appContext.ClaimsPrincipal)
                return;

            _ = localStorage.SetItemAsStringAsync(JWT, jwt);
            AuthenticationState authState = new(appContext.ClaimsPrincipal);
            NotifyAuthenticationStateChanged(Task.FromResult(authState));
        }

        /// <summary>
        /// 标记注销
        /// </summary>
        public void MarkUserAsLoggedOut()
        {
            AuthenticationState authState = new(AnonymousUser);
            _ = localStorage.RemoveItemsAsync(new string[] { JWT });
            NotifyAuthenticationStateChanged(Task.FromResult(authState));
        }
        /// <summary>
        /// 更新JWT Token
        /// </summary>
        /// <param name="userData"></param>
        /// <param name="password">登录密码</param>
        public void UpdateToken(JwtUserData? userData, string password)
        {
            if (null == userData || string.IsNullOrEmpty(userData.UserName))
                return;

            userData.Password = cypto.Encrypt(password);
            userData.JwtTime = DateTime.Now;
            Claim[] claims = new Claim[] {
                new Claim(ClaimTypes.Name, userData.UserName),
                new Claim(ClaimTypes.Expiration, userData.ExpiredTime.ToString(),typeof(DateTime).ToString()),
                new Claim(ClaimTypes.UserData, JsonSerializer.Serialize(userData)),
            };
            DateTime expires = userData.ExpiredTime;
            var jwtToken = new JwtSecurityToken(claims: claims, notBefore: DateTime.Now, expires: expires, signingCredentials: signingCredentials);
            string jwt = tokenHandler.WriteToken(jwtToken);
            appContext.ClaimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, JWT));
            appContext.UserData = userData;
            MarkUserAsAuthenticated(jwt);
        }
        #region private methods
        private async Task<string> GetLocalJwt()
        {
            string jwt = string.Empty;
            try
            {
                jwt = await localStorage.GetItemAsStringAsync(JWT);
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"{ex}", "Exception");
            }
            return jwt;
        }

        private void AppContextInit(JwtUserData? userData)
        {
            if (null == userData)
                return;
            appContext.CurrentUser = userService.GetUserByUserName(userData.UserName);
            appContext.IsDomain = !string.IsNullOrEmpty(userData.Domain);
            appContext.LoginDomain = userData.Domain;
            appContext.LoginPassword = cypto.Decrypt(userData.Password);
            appContext.UserData = userData;
            appContext.LoginName = userData.UserName;
        }

        /// <summary>
        /// 获取本地配置初始化的用户
        /// </summary>
        /// <returns></returns>
        private JwtUserData? GetJwtUserData(string jwt)
        {
            JwtUserData? userData = null;
            try
            {
                IEnumerable<Claim> claims = ParseClaimsFromJwt(jwt);
                Claim? userDataClaim = null;
                if (!claims.Any() || null == (userDataClaim = claims.FirstOrDefault(c => c.Type == ClaimTypes.UserData)))
                    return userData;
                appContext.ClaimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, JWT));
                string? dataJson = claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
                if (!string.IsNullOrEmpty(dataJson))
                    userData = JsonSerializer.Deserialize<JwtUserData>(userDataClaim.Value);
                return userData;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            return userData;
        }

        private static IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
        {
            var payload = jwt.Split('.')[1];
            var jsonBytes = ParseBase64WithoutPadding(payload);
            var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
            if (null == keyValuePairs)
                return Array.Empty<Claim>();

            List<Claim> claims = new();
            foreach (var kv in keyValuePairs)
            {
                string? v = null != kv.Value ? kv.Value.ToString() : string.Empty;
                if (string.IsNullOrWhiteSpace(v))
                    continue;
                claims.Add(new Claim(kv.Key, v));
            }

            return claims;
        }

        private static byte[] ParseBase64WithoutPadding(string base64)
        {
            switch (base64.Length % 4)
            {
                case 2: base64 += "=="; break;
                case 3: base64 += "="; break;
            }
            return Convert.FromBase64String(base64);
        }
        #endregion
    }
}
